
;(function($win){

//Decoder states.
    const decoderStateIdle          = 0;
    const decoderStateInitializing  = 1;
    const decoderStateReady         = 2;
    const decoderStateFinished      = 3;

//Player states.
    const playerStateIdle           = 0;
    const playerStatePlaying        = 1;
    const playerStatePausing        = 2;
    const playerStateStopped        = 3;


    const eventRecordDownloadCompleted = 0x00000000;

//Constant.
    const maxBufferTimeLength       = 1.0;
    const downloadSpeedByteRateCoef = 2.0;


    const VideoFrameLast            = -1;
    const VideoFrameFirst           = 0;
    const VideoFrameMiddle          = 1;

    const isLittleEndian            = true;


    /****frames come from websocket */
    const FRAME_CONFIG              = 0x68643030;
    const FRAME_IDR                 = 0x62643030;
    const FRAME_PARTIAL             = 0x63643030;
    const FRAME_AUDIO               = 0x62773030;
    const FRAME_GPS                 = 0x73706730;
    const FRAME_END                 = 0x481;

    const MAX_VALUE_OF_INT         = 2147483647;

    /*******start decoder.js  code */
    const REQUEST_CODEC_NEW          = 0x6000

    const REQUEST_CODEC_READY        = 0x6001;
    const REQUEST_CODEC_D_DATA       = 0x6002;
    const REQUEST_CODEC_E_DATA       = 0x6003;
    const REQUEST_CODEC_RELEASE      = 0x6004;
    const REQUEST_CODEC_FLUSH        = 0x6005;
    const REQUEST_E_CODEC_NEW        = 0x6006;
    const REQUEST_D_CODEC_NEW        = 0x6007;

    const RESPONSE_CODEC_NEW          = 0x6100;
    const RESPONSE_CODEC_READY        = 0x6101;
    const RESPONSE_CODEC_D_DATA       = 0x6102;
    const RESPONSE_CODEC_E_DATA       = 0x6103;
    const RESPONSE_CODEC_RELEASE      = 0x6104;
    const RESPONSE_CODEC_FLUSH        = 0x6105;
    const RESPONSE_E_CODEC_NEW        = 0x6106;
    const RESPONSE_D_CODEC_NEW        = 0x6107;



    /***connect.js */
    const REQUEST_CONNECT_NEW        = 0x2001;
    const REQUEST_CONNECT_SEND_AUDIO_DATA = 0x2002;

    const RESPONSE_CONNECT_CLOSE       = 0x2102;
    const RESPONSE_CONNECT_ERROR       = 0x2103;
    const RESPONSE_CONNECT_PROGRESS    = 0x2104;
    const RESPONSE_CONNECT_WSERROR     = 0x2105;
    const RESPONSE_CONNECT_DONE        = 0x2106;
    const RESPONSE_CONNECT_READY       = 0x2107;
    const RESPONSE_CONNECT_GPS_DATA    = 0x2109;
    const RESPONSE_CONNECT_SUCCESS     = 0x2112;

    const RESPONSE_CONNECT_MEMBER_LEAVE = 0x2120;

    const RESPONSE_CONNECT_ERROR_GROUPFULL     = 0x2121
    const RESPONSE_CONNECT_ERROR_MEMBERTALKING          = 0x2122
    /*******end decoder.js  code */

    String.prototype.startWith = function(str) {
        var reg = new RegExp("^" + str);
        return reg.test(this);
    };

    function WebAudio(option) {

        //the url of websocket
        this.url                = "";
        //Get GPS data from live stream

        //Get GPS data from history record
        //this.pastGPSCallback    = null;
        //websocket send playParams to play video/audio
        this.playParams         = null;

        this.playerState        = playerStateStopped;
        //playing audio/video
        this.pcmPlayer          = null;

        /*** audio config ***/
        this.audioEncoding      = "16bitInt";
        this.audioChannels      = 1;
        this.audioSampleRate    = 8000;
        this.audioType          = 1; //0:G711a 1:AAC


        this.audioFrameBuffer   = [];
        this.GpsFrameBuffer     = [];
        //loop timestamp in milliseconds,loopTotalTime refered to how many millseconds has pass away
        this.loopNowTime        = 0;
        this.loopLastTime       = 0;
        this.loopTotalTime      = 0;
        this.cacheTotolTime     = 0;

        //tracker timer
        this.requestDecodingTimer       = null;

        this.trackerTimer       = null;
        this.timeLabel          = null;
        this.durationLabel      = null;
        this.timeTrack          = null;
        this.timeCacheTrack     = null;

        this.isMediaServerReady = false;


        //webAudio
        this.audioStream      = null;

        /***codec worker */
        this.codecWorker       = null;

        //customize pts begin from 0;
        this.beginTimeOffset    = -1;

        //loopRender timer
        this.loopRenderTask     = null;

        //playParams
        this.deviceID           = null;

        this.sessionKey         = null;
        this.startOffset        = null;
        this.startTime          = null;
        this.endTime            = null;
        this.GPSCallback        = null;
        this.connectWorker      = null;
        this.recorder           = null;
        //can do audio recording
        this.canAudioRecording = false;
        this.onPlayListener    = null;

        this.gainValue          = 1.0;

        console.info("option",option);
        if(option!=null){
            for(var key in option){
                this[key] = option[key];
            }
        }
    }


    var _renderAudioFrame = function (data) {
        if(!this.pcmPlayer){
            this.pcmPlayer = new PCMPlayer({
                encoding: this.audioEncoding,
                channels: this.audioChannels,
                sampleRate: this.audioSampleRate,
                flushingTime: 5000
            });
            this.pcmPlayer.setVolume(0.4);
            //this.pcmPlayer.setVolume(this.gainValue);
        }
        this.pcmPlayer.play(data);
    };

    var _initAudioStream = function(){
        var self = this;
        $win.SpRecorder.get(_getAudioStreamOk.bind(this),_getAudioStreamFailed.bind(this), {
            dataCallback:_getDataFromAudioStream.bind(this)
        });
        console.info("_initAudioStream checking");
    }

    var _getAudioStreamOk = function(stream){
        var self = this;
        self.audioStream = stream;
        var data = {
            type : REQUEST_E_CODEC_NEW,
            audioType  :  self.audioType,
            sampleRate :  self.audioStream.getSampleRate(),
            channelCount : self.audioStream.getChannels()
        };
        self.codecWorker.postMessage(data);
        self.audioStream.start();
        self._notifyPlayRecordingStart();
        console.info("_initAudioStream ok");
    }

    var _getAudioStreamFailed = function(error){
        console.info(error);
        this.audioStream = null;
        this._notifyPlayRecordingFailed(-1);
        console.info("_initAudioStream failed");
    }

    var _getDataFromAudioStream = function(rawData){
        var self = this;
        if(self.codecWorker == null) return;
        var data = {
            type  : REQUEST_CODEC_E_DATA,
            data  : rawData.buffer,
            pts   : 0,
            index : 0
        };
        self.codecWorker.postMessage(data,[data.data]);
    }


//onPlaySuccess
    var _notifyPlaySuccess = function(){
        console.info("_notifyPlaySuccess");
        if(this.onPlayListener != null) this.onPlayListener.onPlaySuccess();
    }
    //onPlayMemberLeave
    var _notifyPlayMemberLeave = function(data){
        console.info("_notifyPlayMemberLeave");
        if(this.onPlayListener != null) this.onPlayListener.onPlayMemberLeave(data);
    }
//onPlayFailed
    var _notifyPlayFailed = function(errno,data){
        if(this.onPlayListener != null) this.onPlayListener.onPlayFailed(errno,data);
        console.info("_notifyPlayFailed");
    }
//onPlayStop
    var _notifyPlayStop = function(autoStop){
        if(this.onPlayListener != null) this.onPlayListener.onPlayStop(autoStop);
        console.info("_notifyPlayStop");
    }
//onPlayRecordingStart
    var _notifyPlayRecordingStart = function(){
        if(this.onPlayListener != null) this.onPlayListener.onPlayRecordingStart();
        console.info("_notifyPlayRecordingStart");
    }
//onPlayRecordingStop
    var _notifyPlayRecordingStop = function(){
        if(this.onPlayListener != null) this.onPlayListener.onPlayRecordingStop();
        console.info("_notifyPlayRecordingStop");
    }
//onPlayRecordingFailed
    var _notifyPlayRecordingFailed = function(errno){
        if(this.onPlayListener != null) this.onPlayListener.onPlayRecordingFailed(errno);
        console.info("_notifyPlayRecordingFailed");
    }

    /**
     *
     * @params data  {videoType:1,
     * {audioType:1,sampleRate:8000,channelCount: 1}
     */
    var _initCodec = function(){
        var self = this;
        if(this.codecWorker != null) return ;
        this.codecWorker = new Worker("./static/player/codec.js");
        this.codecWorker.onmessage = function (evt) {
            var data = evt.data;
            switch (data.type) {
                case RESPONSE_CODEC_READY:
                    var data = {
                        type : REQUEST_CODEC_NEW,
                        audioType  :  self.audioType,
                        sampleRate :  self.audioSampleRate,
                        channelCount : self.audioChannels
                    };
                    self.codecWorker.postMessage(data);
                    console.info("codecWorker ready")
                    break;
                case RESPONSE_E_CODEC_NEW:
                    console.info("RESPONSE_E_CODEC_NEW")
                    break;
                case RESPONSE_CODEC_D_DATA:
                    //testFile(data.data);
                    self._pushFrameBuffer(data.data);
                    break;
                case RESPONSE_CODEC_RELEASE:
                    self._onDecodeFinished(data);
                    break;
                case RESPONSE_CODEC_D_DATA:
                    self._onReceiveDecodeFrame(data);
                    break;
                case RESPONSE_CODEC_E_DATA:
                    console.info("RESPONSE_CODEC_E_DATA",data);
                    // testFile(data.data); return ;
                    self.connectWorker && self.connectWorker.postMessage(
                        {
                            type:REQUEST_CONNECT_SEND_AUDIO_DATA,
                            data:data.data
                        },[data.data.buffer]
                    )
                    break;
                case RESPONSE_CODEC_NEW:
                    console.info("codecWorker new")
                    break;
            }
        }
        console.info("new Worker - codec.js");
    }

    var _initConnect = function(){
        var self = this;
        if(this.connectWorker != null) return ;
        this.connectWorker = new Worker("./static/player/connect.js");
        this.connectWorker.onmessage = function (evt) {
            var data = evt.data;
            switch (data.type) {
                case REQUEST_CODEC_D_DATA:
                    self.codecWorker.postMessage(data,[data.data]);
                    break;
                case RESPONSE_CONNECT_GPS_DATA:
                    self.GpsFrameBuffer.push(data);
                    break;
                case RESPONSE_CONNECT_CLOSE:
                    console.info("RESPONSE_CONNECT_CLOSE");
                    var autoStop = true;
                    self.stop(autoStop);
                    break;
                case RESPONSE_CONNECT_WSERROR:
                    self._notifyPlayFailed(-3);
                    console.info("RESPONSE_CONNECT_WSERROR");
                    self.stop(autoStop);
                    break;
                case RESPONSE_CONNECT_ERROR:
                    self._notifyPlayFailed(-4,data.data);
                    console.info("RESPONSE_CONNECT_ERROR");
                    self.stop(autoStop);
                    break;
                case RESPONSE_CONNECT_ERROR_GROUPFULL:
                    self._notifyPlayFailed(-1,data.data);
                    console.info("RESPONSE_CONNECT_ERROR");
                    self.stop(autoStop);
                    break;
                case RESPONSE_CONNECT_ERROR_MEMBERTALKING:
                    self._notifyPlayFailed(-2,data.data);
                    console.info("RESPONSE_CONNECT_ERROR");
                    self.stop(autoStop);
                    break;
                case RESPONSE_CONNECT_MEMBER_LEAVE:
                    self._notifyPlayMemberLeave(data.data);
                    console.info("RESPONSE_CONNECT_MEMBER_LEAVE",data.data);
                    break;
                case RESPONSE_CONNECT_SUCCESS:
                    self._notifyPlaySuccess();
                    console.info("RESPONSE_CONNECT_SUCCESS",self.onPlayListener,self);
                    break;
                case RESPONSE_CONNECT_READY:
                    console.info("connector ready")
                    self.connectWorker.postMessage({
                        type:REQUEST_CONNECT_NEW,
                        option:{
                            url:self.url,
                            playParams:self.playParams,
                            audioType:self.audioType
                        }
                    });

                    break;
            }
        }
        console.info("new Worker - connector.js");
    }

    var _onReceiveDecodeFrame = function(data){
        // console.info("_onReceiveDecodeFrame isVideo=",!data.isAudio);
        this._pushFrameBuffer(data);
    }

    var startRecording = function(){
        this._initAudioStream();
        console.info("startRecording")
    }

    var stopRecording = function(){
        this.audioStream && this.audioStream.stop();
        this.audioStream = null;
        this._notifyPlayRecordingStop();
        console.info("stopRecording")
    }

    var isRecording = function(){
        return this.audioStream != null;
    }

    var canRecording = function(){
        return this.canRecording;
    }

    /**
     *
     * @param {*} audioType
     * @param {*} deviceIDs
     * @param {*} sessionKey
     * @param {*} IOnPlayListener
     * interface IOnPlayListener{
     *    fun onPlaySuccess();
     *    fun onPlayFailed(int errno);
     *    fun onPlayStop();
     *    fun onGPSData();
     *    fun onAudioStart();
     *    fun onAudioStop();
     * }
     *
     * @returns
     */
    var playTalk = function(audioType,deviceIDs,sessionKey,nWorkMode,onPlayListener){
        if(!deviceIDs  || !sessionKey ){
            console.error("deviceIDs,sessionKey must not be null");
            return false;
        }
        this._setStopped();
        this.playerState = playerStatePlaying;
        this.canAudioRecording = true;
        this.onPlayListener = onPlayListener;
        console.info("onPlayListener3",onPlayListener,this.onPlayListener)
        //this.GPSCallback = onGPSCallback || null;
        if (nWorkMode === 4){
            deviceIDs.push('PC12345');
        }
        this.deviceIDs =  deviceIDs;
        this.sessionKey = sessionKey;
        this.audioType = (audioType == "AAC" || audioType == "aac")  ?  1 : 0

        this.playParams = JSON.stringify(
            {
                "messageID": 710,
                "nLength": 11,
                "nQueue": 1,
                "nParameter1": 0,
                "nParameter2": 0,
                "webstring": "abcefghijk",
                "nWorkMode": nWorkMode, //0 talk ;3 monitor
                "usControl": 1, // 0 cancel ;1 apply
                "usChannel": 1,//channel number started from 1
                "nDeviceNumber": deviceIDs.length,//
                "nSessionID": sessionKey,//Session
                "szDeviceID": deviceIDs,//[100081,'']
            }
        );
        console.info("request talk",this.playParams);
        this._initCodec();
        this._initConnect();

        return true;
    }


    var playMonitor = function(audioType,deviceIDs,sessionKey,onPlayListener){
        if(!deviceIDs || !sessionKey ){
            console.error("deviceIDs,channel,sessionKey must not be null");
            return false;
        }
        this._setStopped();
        this.onPlayListener = onPlayListener;
        this.playerState = playerStatePlaying;
        //this.GPSCallback = GPSCallback || null;
        this.deviceIDs=  deviceIDs;
        this.sessionKey = sessionKey;
        this.canAudioRecording = false;
        this.audioType = (audioType == "AAC" || audioType == "aac")  ?  1 : 0

        this.playParams = JSON.stringify(
            {
                "messageID": 710,
                "nLength": 11,
                "nQueue": 1,
                "nParameter1": 0,
                "nParameter2": 0,
                "webstring": "abcefghijk",
                "nWorkMode": 3, //0 talk ;3 monitor
                "usControl": 1, // 0 cancel ;1 apply
                "usChannel": 1,//channel number started from 1
                "nDeviceNumber": deviceIDs.length,//
                "nSessionID": sessionKey,//Session
                "szDeviceID": deviceIDs,
            }
        );
        console.info("request monitor",this.playParams);
        this._initCodec();
        this._initConnect();
        return true;
    }

    var pause = function(){
        this.recorder.pause()
    }

    var resume = function(){
        this.recorder.resume()
    }


    var stop = function(autoStop){
        autoStop = !!autoStop
        this._setStopped(autoStop);
    }

    var isPaused = function(){
        return this.playerState == playerStatePausing;
    }

    var isPlaying = function(){
        return this.playerState == playerStatePlaying;
    }

    var isStopped = function(){
        return this.playerState == playerStateStopped;
    }

    var isIdle = function(){
        return this.playerState == playerStateIdle;
    }

    var _setStopped = function(autoStop){
        if(this.isStopped()) return ;
        byteBuffer = null;
        this.playerState = playerStateStopped;

        if(this.audioStream != null){
            this.audioStream.stop();
            this.audioStream = null;
        }
        /**send message to fetch worker delete cache files */
        if(this.connectWorker != null){
            this.connectWorker.terminate();
            this.connectWorker = null;
        }

        //stop decoding ,when receive callback terminate the decodeWorker
        if(this.codecWorker != null){
            this.codecWorker.terminate();
            this.codecWorker = null;
        }

        this.loopRenderTask && cancelAnimationFrame(this.loopRenderTask);
        this.loopRenderTask     = null;
        //playParams
        this.deviceIDs           = null;

        this.sessionKey         = null;
        this.GPSCallback        = null;

        this.canAudioRecording = false;

        //this.GPSCallback = onGPSCallback || null;


        /*** audio config ***/
        this.audioEncoding      = "16bitInt";
        this.audioChannels      = 1;
        this.audioSampleRate    = 8000;
        this.audioType          = 1; //1:G711a 2:AAC

        this.audioFrameBuffer   = [];
        this.GpsFrameBuffer     = [];

        this.loopNowTime        = 0;
        this.loopLastTime       = 0;
        this.loopTotalTime      = 0;

        if(this.pcmPlayer!=null){
            this.pcmPlayer.destroy();
            this.pcmPlayer = null;
        }

        console.info("webaudio stopped");
        this._notifyPlayStop(autoStop);
        this.onPlayListener = null;
    }

    var release = function(){
        this._setStopped();
        console.info("WebAudio released");
    }

    /**
     *
     * @param {isAudio:true,data:data,pts:pts,index:(0||-1||1)} obj
     * @field isAudio refers to very obvious
     * @field data refers to raw data like PCM or YUV420
     * @field pts refers to when to render
     * @field index has generally 4 four (-1||0||1) -1:end frame(can not be rendered), 0: first frame ,1:middle frame
     * @desc [first frame and last frame] must be video frame ,so isAudio value of the frame is false
     */
    var downloadOnce = false;
    var byteBuffer = null;
    var testFile = function(data){
        if(byteBuffer == null){
            byteBuffer = data.slice(0);
        }else{
            var tmp = new Uint8Array(byteBuffer.byteLength + data.byteLength);
            tmp.set(new Uint8Array(byteBuffer), 0);
            tmp.set(new Uint8Array(data.slice(0)), byteBuffer.byteLength);
            byteBuffer = tmp.buffer
        }
        if(!downloadOnce && byteBuffer.byteLength >=  500  * 1024){
            console.error("download........")
            downloadFile(byteBuffer,"video/mp4", "blank.pcm")
            downloadOnce = true;
        }
    }

    var _pushFrameBuffer = function(obj){
        this._renderAudioFrame(obj);
    }

    var _loopRender = function(){
        if(this.isStopped()) return ;
        this.loopNowTime = Date.now();
        const pastTime = this.loopLastTime > 0 ? this.loopNowTime - this.loopLastTime : 0;
        this.loopLastTime = this.loopNowTime;

        var self = this;
        this.loopRenderTask = requestAnimationFrame(_loopRender.bind(this))
        if(!this.isPlaying()) {
            console.info("the player state is not playing,so have to stop loopRender");
            return ;
        }
        const audioFrameLength = this.audioFrameBuffer.length;

        if(this.loopTotalTime >= this.duration){
            this._setStopped(true);
            return ;
        }
        if(this.GpsFrameBuffer.length > 0){
            var gpsObj = this.GpsFrameBuffer[0];
            if(gpsObj.pts <= this.loopTotalTime){
                this.GpsFrameBuffer.shift();
                console.info("gps callback1",gpsObj,self.GPSCallback);
                self.GPSCallback && setTimeout(function(){
                    self.GPSCallback(gpsObj);
                },20);
            }
        }
        if(audioFrameLength  > 0 ){
            //caculate duration of every requestAnimatedFrame
            var frame = null;
            this.loopTotalTime += pastTime;

            if(audioFrameLength > 0){
                frame = this.audioFrameBuffer[0];
                //console.info("9999","renderAudio,pts=",frame.pts,"loopTotalTime=",this.loopTotalTime);
                if(frame.pts <= this.loopTotalTime){
                    this.audioFrameBuffer.shift();
                    this._renderAudioFrame(frame);
                    //console.info("9999 renderAudioFrame ,pts=%d,loopTotalTime=%d,bufferSize=%d",frame.pts,this.loopTotalTime,this.videoFrameBuffer.length);
                }
            }

        }
    }



    var methods = {
        /*****external api*****/
        playTalk:playTalk,
        playMonitor:playMonitor,
        //pause:pause,
        //resume:resume,
        stop :stop,
        //isPaused:isPaused,
        isPlaying:isPlaying,
        isStopped:isStopped,
        startRecording:startRecording,
        stopRecording:stopRecording,
        isRecording:isRecording,
        canRecording:canRecording,
        //setVolume:_setVolume,
        release:release,
        /******end external api******/
        /*****internal api******/
        //_getDistanceOfFrameBuffer,_getDistanceOfFrameBuffer,
        _notifyPlayMemberLeave:_notifyPlayMemberLeave,
        _notifyPlaySuccess:_notifyPlaySuccess,
        _notifyPlayFailed:_notifyPlayFailed,
        _notifyPlayStop:_notifyPlayStop,
        _notifyPlayRecordingStart:_notifyPlayRecordingStart,
        _notifyPlayRecordingStop:_notifyPlayRecordingStop,
        _notifyPlayRecordingFailed:_notifyPlayRecordingFailed,
        _loopRender,_loopRender,
        _pushFrameBuffer:_pushFrameBuffer,
        //_onTerminateDecoder:_onTerminateDecoder,
        _setStopped:_setStopped,
        //_initWebAudio:_initWebAudio,
        _initCodec:_initCodec,
        _initConnect:_initConnect,
        _renderAudioFrame:_renderAudioFrame,
        _initAudioStream : _initAudioStream,
        _getAudioStreamOk : _getAudioStreamOk,
        _getAudioStreamFailed: _getAudioStreamFailed,
        _getDataFromAudioStream:_getDataFromAudioStream
        /****end internal api *****/
    }

    var __fn__ = WebAudio.prototype

    for (let method in methods) {
        __fn__[method] = methods[method];
    }

    $win.WebAudio = WebAudio;

})(window);




/***********************START Helper *****************
 var getDuration = function(startTime,endTime){
    // Convert to Date objects
    const startDate = new Date(startTime);
    const endDate = new Date(endTime);
    // Calculate the time difference in milliseconds
    return Math.abs(endDate - startDate);
}

 var formatTime = function (s) {
    s /= 1000;
    var h = Math.floor(s / 3600) < 10 ? '0' + Math.floor(s / 3600) : Math.floor(s / 3600);
    var m = Math.floor((s / 60 % 60)) < 10 ? '0' + Math.floor((s / 60 % 60)) : Math.floor((s / 60 % 60));
    var s = Math.floor((s % 60)) < 10 ? '0' + Math.floor((s % 60)) : Math.floor((s % 60));
    return result = h + ":" + m + ":" + s;
};

 var getSeekTime = function(startTimeStr,seconds){
    const date0 = new Date(startTimeStr);
    date0.setSeconds(date0.getSeconds() + seconds);
    return date0.format("yyyy-MM-dd hh:mm:ss");
}

 Date.prototype.format = function(fmt)
 {
    var o = {
        "M+" : this.getMonth()+1,
        "d+" : this.getDate(),
        "h+" : this.getHours(),
        "m+" : this.getMinutes(),
        "s+" : this.getSeconds(),
        "q+" : Math.floor((this.getMonth()+3)/3),
        "S"  : this.getMilliseconds()
    };
    if(/(y+)/.test(fmt))
        fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
    for(var k in o)
        if(new RegExp("("+ k +")").test(fmt))
    fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
return fmt;
}


 var downloadFile = function(data,type,filename){
    var blob;
    console.info("window",window);
    if(typeof window.Blob == "function"){
        blob = new Blob([data],{type:type});
    }else{
        var BlobBuilder = window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder || window.MSBlobBuilder;
        var blobBuilder = new BlobBuilder();
        blobBuilder.append(data);
        blob = blobBuilder.getBlob(type);
    }

    var URL = window.URL || window.webkitURL;
    var url = URL.createObjectURL(blob);
    var anchor = document.createElement("a");
    if('download' in anchor){
        anchor.style.visibility = "hidden";
        anchor.href = url;
        anchor.download = filename;
        document.body.appendChild(anchor);
        var evt = document.createEvent("MouseEvents");
        evt.initEvent("click",true,true);
        anchor.dispatchEvent(evt);
        document.body.removeChild(anchor);
    }else if(navigator.MSBlobBuilder){
         navigator.msSaveBlob(blob,filename);
    }else{
        location.href = url;
    }
}
 *********/

/***********************END Helper **************************/
